﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Linq;
using ZhukBlogLinqExamples.Model.Meta;

namespace ZhukBlogLinqExamples.Model
{
    /// <summary>
    /// Base data entity
    /// </summary>
    [ContentType(Name = "Элемент", Id = "0x01")]
    [Serializable]
    [DerivedEntityClass(Type = typeof(Branch))]
    [DerivedEntityClass(Type = typeof(Company))]
    [DerivedEntityClass(Type = typeof(Department))]
    [DerivedEntityClass(Type = typeof(Employee))]
    public class ZhukDataItem : ITrackEntityState, ITrackOriginalValues, INotifyPropertyChanged, INotifyPropertyChanging, ICustomMapping
    {
        #region Private Fields

        private int? _id;
        private int? _updateById;
        private string _updateBy;
        private Guid _guid;
        private int? _version;
        private string _path;
        private EntityState _entityState;
        private IDictionary<string, object> _originalValues;
        private string _title;
        private DateTime? _createdDate;
        private string _createdBy;
        private int? _createdById;
        private DateTime? _updateDate;
        private string _serverUrl;
        private string _baseName;
        private bool? _hasAttachments;
        private SPAttachmentCollection _contentAttachments;
        private string _moderationComments;
        private bool? _isCurrentVersion;

        #endregion

        /// <summary>
        /// Create a blank instance
        /// </summary>
        public ZhukDataItem() { }

       

        /// <summary>
        /// Create an instance of class from SPitem
        /// </summary>
        /// <param name="item">SPItem instance</param>
        public ZhukDataItem(SPItem item)
        {
            if (item == null) return;
            var objType = GetType();
            var properties = objType.GetProperties();
            foreach (var property in properties)
            {
                var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
                foreach (ColumnAttribute att in attributes)
                {
                    var field = objType.GetField(att.Storage,
                        BindingFlags.NonPublic | BindingFlags.Instance);
                    while (field == null)
                    {
                        objType = objType.BaseType;
                        if (objType == null) break;
                        field = objType.GetField(att.Storage,
                            BindingFlags.NonPublic | BindingFlags.Instance);
                    }
                    if (field != null)
                    {
                        switch (att.FieldType)
                        {
                            case "Lookup":
                                try
                                {
                                    var fv = new SPFieldLookupValue(
                                        (item[att.Name] ?? string.Empty).ToString());
                                    if (att.IsLookupId)
                                    {
                                        field.SetValue(this, fv.LookupId);
                                    }
                                    else
                                    {
                                        field.SetValue(this, fv.LookupValue);
                                    }
                                }
                                catch (ArgumentException)
                                {
                                    field.SetValue(this, item[att.Name]);
                                }
                                break;
                            case "User":
                                try
                                {
                                    var fv = new SPFieldUserValue(item.Fields.List.ParentWeb,
                                                                  (item[att.Name] ?? string.Empty).ToString());
                                    if (att.IsLookupId)
                                    {
                                        field.SetValue(this, fv.LookupId);
                                    }
                                    else
                                    {
                                        field.SetValue(this, fv.LookupValue);
                                    }
                                }
                                catch (ArgumentException)
                                {
                                    field.SetValue(this, item[att.Name]);
                                }
                                break;
                            case "Guid":
                                field.SetValue(this, new Guid(item[att.Name].ToString()));
                                break;
                            default:
                                field.SetValue(this, item[att.Name]);
                                break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Create an instance of class from SPitem
        /// </summary>
        /// <param name="item">SPListItemVersion instance</param>
        public ZhukDataItem(SPListItemVersion item)
        {
            if (item == null) return;
            var objType = GetType();
            var properties = objType.GetProperties();
            foreach (var property in properties)
            {
                var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
                foreach (ColumnAttribute att in attributes)
                {
                    var field = objType.GetField(att.Storage,
                        BindingFlags.NonPublic | BindingFlags.Instance);
                    while (field == null)
                    {
                        objType = objType.BaseType;
                        if (objType == null) break;
                        field = objType.GetField(att.Storage,
                            BindingFlags.NonPublic | BindingFlags.Instance);
                    }
                    if (field != null)
                    {
                        switch (att.FieldType)
                        {
                            case "Lookup":
                                try
                                {
                                    var fv = new SPFieldLookupValue(
                                        (item[att.Name] ?? string.Empty).ToString());
                                    if (att.IsLookupId)
                                    {
                                        field.SetValue(this, fv.LookupId);
                                    }
                                    else
                                    {
                                        field.SetValue(this, fv.LookupValue);
                                    }
                                }
                                catch (ArgumentException)
                                {
                                    field.SetValue(this, item[att.Name]);
                                }
                                break;
                            case "User":
                                try
                                {
                                    var fv = new SPFieldUserValue(item.Fields.List.ParentWeb,
                                                        (item[att.Name] ?? string.Empty).ToString());
                                    if (att.IsLookupId)
                                    {
                                        field.SetValue(this, fv.LookupId);
                                    }
                                    else
                                    {
                                        field.SetValue(this, fv.LookupValue);
                                    }
                                }
                                catch (ArgumentException)
                                {
                                    field.SetValue(this, item[att.Name]);
                                }
                                break;
                            case "Guid":
                                field.SetValue(this, new Guid(item[att.Name].ToString()));
                                break;
                            default:
                                field.SetValue(this, item[att.Name]);
                                break;
                        }
                    }
                }
            }
            IsCurrentVersion = false;
        }

        /// <summary>
        /// Generate batch-command for creating/changing element
        /// </summary>
        /// <param name="listId">List Id</param>
        /// <returns>Batch-command</returns>
        public string GetBatchSaveCommand(Guid listId)
        {
            var objType = GetType();
            var properties = objType.GetProperties();
            var sb = new StringBuilder(10);
            sb.AppendFormat(@"<Method ID=""{0}, Save"">{1}",
                Id.HasValue ? GUID : Guid.NewGuid(),
                Environment.NewLine);
            sb.AppendFormat(@"<SetList>{0}</SetList>{1}",
                listId,
                Environment.NewLine);
            sb.AppendFormat(@"<SetVar Name=""ID"">{0}</SetVar>{1}",
                Id.HasValue ? Id.Value.ToString() : "New",
                Environment.NewLine);
            sb.AppendLine(@"<SetVar Name=""Cmd"">Save</SetVar>");

            sb.AppendFormat(@"<SetVar Name=""RootFolder"">{0}</SetVar>{1}",
                Path,
                Environment.NewLine);
            foreach (var property in properties)
            {
                var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
                foreach (ColumnAttribute att in attributes)
                {
                    if (att.ReadOnly)
                        continue;
                    var field = objType.GetField(att.Storage,
                                                    BindingFlags.NonPublic | BindingFlags.Instance);
                    while (field == null)
                    {
                        objType = objType.BaseType;
                        if (objType == null) break;
                        field = objType.GetField(att.Storage,
                                                    BindingFlags.NonPublic | BindingFlags.Instance);
                    }
                    if (field != null)
                    {
                        sb.AppendFormat(@"<SetVar Name=""{0}{1}"">{2}</SetVar>{3}",
                            BatchFieldPrefix,
                            att.Name,
                            field.GetValue(this),
                            Environment.NewLine);
                    }
                }
            }
            sb.AppendLine(@"</Method>");
            return sb.ToString();
        }

        /// <summary>
        /// Generate batch-command for deleting element
        /// </summary>
        /// <param name="listId">List Id</param>
        /// <returns>Batch-command</returns>
        public string GetBatchDeleteCommand(Guid listId)
        {
            var sb = new StringBuilder(10);
            sb.AppendFormat(@"<Method ID=""{0}, Delete"">{1}",
                GUID,
                Environment.NewLine);
            sb.AppendFormat(@"<SetList>{0}</SetList>{1}",
                listId,
                Environment.NewLine);
            sb.AppendFormat(@"<SetVar Name=""ID"">{0}</SetVar>{1}",
                Id,
                Environment.NewLine);
            sb.AppendLine(@"<SetVar Name=""Cmd"">Delete</SetVar>");
            sb.AppendLine(@"</Method>");
            return sb.ToString();
        }

        /// <summary>
        /// Field names prefix from batch-command
        /// </summary>
        private const string BatchFieldPrefix = "urn:schemas-microsoft-com:office:office#";

        /// <summary>
        /// State of current instance
        /// </summary>
        public EntityState EntityState
        {
            get
            {
                return _entityState;
            }
            set
            {
                if ((value != _entityState))
                {
                    _entityState = value;
                }
            }
        }

        IDictionary<string, object> ITrackOriginalValues.OriginalValues
        {
            get
            {
                if ((null == _originalValues))
                {
                    _originalValues = new Dictionary<string, object>();
                }
                return _originalValues;
            }
        }

        #region LINQ Columns

        /// <summary>
        /// Boolean flag meaning is this entity has attachments
        /// </summary>
        [Column(Name = "Attachments", Storage = "_hasAttachments", ReadOnly = true, FieldType = "Attachments")]
        public bool? HasAttachments
        {
            get
            {
                return _hasAttachments;
            }
            set
            {
                if ((value == _hasAttachments)) return;
                OnPropertyChanging("HasAttachments", _hasAttachments);
                _hasAttachments = value;
                OnPropertyChanged("HasAttachments");
            }
        }

        /// <summary>
        /// Is this entity is current version flag
        /// </summary>
        [Column(Name = "_IsCurrentVersion", Storage = "_isCurrentVersion", ReadOnly = true, FieldType = "Boolean")]
        public bool? IsCurrentVersion
        {
            get
            {
                return _isCurrentVersion;
            }
            set
            {
                if ((value == _isCurrentVersion)) return;
                OnPropertyChanging("IsCurrentVersion", _isCurrentVersion);
                _isCurrentVersion = value;
                OnPropertyChanged("IsCurrentVersion");
            }
        }

        /// <summary>
        /// ID 
        /// </summary>
        [Column(Name = "ID", Storage = "_id", ReadOnly = true, FieldType = "Counter")]
        public int? Id
        {
            get
            {
                return _id;
            }
            set
            {
                if ((value == _id)) return;
                OnPropertyChanging("Id", _id);
                _id = value;
                OnPropertyChanged("Id");
            }
        }

        /// <summary>
        /// Version
        /// </summary>
        [Column(Name = "owshiddenversion", Storage = "_version", ReadOnly = true, FieldType = "Integer")]
        public int? Version
        {
            get
            {
                return _version;
            }
            set
            {
                if ((value == _version)) return;
                OnPropertyChanging("Version", _version);
                _version = value;
                OnPropertyChanged("Version");
            }
        }

        /// <summary>
        /// Path to folder containing this entity
        /// </summary>
        [Column(Name = "FileDirRef", Storage = "_path", ReadOnly = true, FieldType = "Lookup", IsLookupValue = true)]
        public string Path
        {
            get
            {
                return _path;
            }
            set
            {
                if ((value == _path)) return;
                OnPropertyChanging("Path", _path);
                _path = value;
                OnPropertyChanged("Path");
            }
        }

        /// <summary>
        /// Title
        /// </summary>
        [Column(Name = "Title", Storage = "_title", Required = true, FieldType = "Text")]
        public virtual string Title
        {
            get
            {
                return _title;
            }
            set
            {
                if ((value == _title)) return;
                OnPropertyChanging("Title", _title);
                _title = value;
                OnPropertyChanged("Title");
            }
        }

        /// <summary>
        /// Author' name
        /// </summary>
        [Column(Name = "Author", Storage = "_createdBy", ReadOnly = true, FieldType = "User", IsLookupValue = true)]
        public string CreatedBy
        {
            get
            {
                return _createdBy;
            }
            set
            {
                if ((value == _createdBy)) return;
                OnPropertyChanging("CreatedBy", _createdBy);
                _createdBy = value;
                OnPropertyChanged("CreatedBy");
            }
        }

        /// <summary>
        /// Author' Id
        /// </summary>
        [Column(Name = "Author", Storage = "_createdById", ReadOnly = true, FieldType = "User", IsLookupId = true)]
        public int? CreatedById
        {
            get
            {
                return _createdById;
            }
            set
            {
                if ((value == _createdById)) return;
                OnPropertyChanging("CreatedById", _createdById);
                _createdById = value;
                OnPropertyChanged("CreatedById");
            }
        }

        /// <summary>
        /// Created date
        /// </summary>
        [Column(Name = "Created", Storage = "_createdDate", ReadOnly = true, FieldType = "DateTime")]
        public DateTime? CreatedDate
        {
            get
            {
                return _createdDate;
            }
            set
            {
                if ((value == _createdDate)) return;
                OnPropertyChanging("CreatedDate", _createdDate);
                _createdDate = value;
                OnPropertyChanged("CreatedDate");
            }
        }

        /// <summary>
        /// Editor' name
        /// </summary>
        [Column(Name = "Editor", Storage = "_updateBy", ReadOnly = true, FieldType = "User", IsLookupValue = true)]
        public string UpdateBy
        {
            get
            {
                return _updateBy;
            }
            set
            {
                if ((value == _updateBy)) return;
                OnPropertyChanging("UpdateBy", _updateBy);
                _updateBy = value;
                OnPropertyChanged("UpdateBy");
            }
        }

        /// <summary>
        /// Editor' Id
        /// </summary>
        [Column(Name = "Editor", Storage = "_updateById", ReadOnly = true, FieldType = "User", IsLookupId = true)]
        public int? UpdateById
        {
            get
            {
                return _updateById;
            }
            set
            {
                if ((value == _updateById)) return;
                OnPropertyChanging("UpdateById", _updateById);
                _updateById = value;
                OnPropertyChanged("UpdateById");
            }
        }

        /// <summary>
        /// Last modified date
        /// </summary>
        [Column(Name = "Modified", Storage = "_updateDate", ReadOnly = true, FieldType = "DateTime")]
        public DateTime? UpdateDate
        {
            get
            {
                return _updateDate;
            }
            set
            {
                if ((value == _updateDate)) return;
                OnPropertyChanging("UpdateDate", _updateDate);
                _updateDate = value;
                OnPropertyChanged("UpdateDate");
            }
        }

        /// <summary>
        /// Unique identifier
        /// </summary>
        [Column(Name = "GUID", Storage = "_guid", ReadOnly = true, FieldType = "Guid")]
        public Guid GUID
        {
            get
            {
                return _guid;
            }
            set
            {
                if ((value == _guid)) return;
                OnPropertyChanging("GUID", _guid);
                _guid = value;
                OnPropertyChanged("GUID");
            }
        }

        /// <summary>
        /// Server-relative URL
        /// </summary>
        [Column(Name = "ServerUrl", Storage = "_serverUrl",
         ReadOnly = true, FieldType = "Computed")]
        public string ServerUrl
        {
            get
            {
                return _serverUrl;
            }
            set
            {
                if ((value == _serverUrl)) return;
                OnPropertyChanging("ServerUrl", _serverUrl);
                _serverUrl = value;
                OnPropertyChanged("ServerUrl");
            }
        }

        /// <summary>
        /// Moderation comments
        /// </summary>
        [Column(Name = "_ModerationComments", Storage = "_moderationComments",
         ReadOnly = true, FieldType = "Note")]
        public string ModerationComments
        {
            get
            {
                return _moderationComments;
            }
            set
            {
                if ((value == _moderationComments)) return;
                OnPropertyChanging("ModerationComments", _moderationComments);
                _moderationComments = value;
                OnPropertyChanged("ModerationComments");
            }
        }

        /// <summary>
        /// Base name. Without path and extension
        /// </summary>
        [Column(Name = "BaseName", Storage = "_baseName",
         ReadOnly = true, FieldType = "Computed")]
        public string BaseName
        {
            get
            {
                return _baseName;
            }
            set
            {
                if ((value == _baseName)) return;
                OnPropertyChanging("BaseName", _baseName);
                _baseName = value;
                OnPropertyChanged("BaseName");
            }
        }

        #endregion

        /// <summary>
        /// Document icon URL
        /// </summary>
        public string IconUrl
        {
            get
            {
                var ext = System.IO.Path.GetExtension(ServerUrl);
                if (ext.Length > 0)
                {
                    ext = ext.Substring(1);
                    return DocumentIcons.GetIconUrl(ext);
                }
                return DocumentIcons.GetIconUrl(string.Empty);
            }
        }

        ///<summary>
        ///</summary>
        public event PropertyChangedEventHandler PropertyChanged;

        ///<summary>
        ///</summary>
        public event PropertyChangingEventHandler PropertyChanging;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if ((null != PropertyChanged))
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnPropertyChanging(string propertyName, object value)
        {
            if ((null == _originalValues))
            {
                _originalValues = new Dictionary<string, object>();
            }
            if ((false == _originalValues.ContainsKey(propertyName)))
            {
                _originalValues.Add(propertyName, value);
            }
            if ((null != PropertyChanging))
            {
                PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Attachments of item
        /// </summary>
        public SPAttachmentCollection Attachments
        {
            get
            {
                return _contentAttachments;
            }
            set
            {
                if (value == _contentAttachments) return;
                if (_contentAttachments == null)
                {
                    _contentAttachments = value;
                    return;
                }
                OnPropertyChanging("Attachments", _contentAttachments);
                _contentAttachments = value;
                OnPropertyChanged("Attachments");
            }
        }

        #region ICustomMapping

        [CustomMapping(Columns = new[] { "Attachments" })]
        public void MapFrom(object listItem)
        {
            var item = (SPListItem)listItem;
            var list = item.ParentList;
            if (list.EnableAttachments)
            {
                Attachments = item.Attachments;
            }
        }

        public void MapTo(object listItem)
        {

        }

        public void Resolve(RefreshMode mode, object originalListItem, object databaseListItem)
        {
            var originalItem = originalListItem as SPListItem;
            var databaseItem = databaseListItem as SPListItem;
            if (databaseItem == null || originalItem == null) return;
            var originalValue = originalItem["Attachments"] as SPAttachmentCollection;
            var databaseValue = databaseItem["Attachments"] as SPAttachmentCollection;
            switch (mode)
            {
                case RefreshMode.KeepChanges:
                    if (_contentAttachments != originalValue)
                    {
                        databaseItem["Attachments"] = _contentAttachments;
                    }
                    else if (_contentAttachments == originalValue &&
                        _contentAttachments != databaseValue)
                    {
                        _contentAttachments = databaseValue;
                    }
                    break;
                case RefreshMode.KeepCurrentValues:
                    databaseItem["Attachments"] = _contentAttachments;
                    break;
                case RefreshMode.OverwriteCurrentValues:
                    _contentAttachments = databaseValue;
                    break;
                default:
                    break;
            }
        }

        #endregion
    }
}
